Public health benefits of clean household energy in India

An interactive plot visualising how a complete transition to clean household energy in India can save one-quarter of the healthy life lost to particulate matter pollution exposure in India.

For more information, see the paper here.

Scenarios

  • BASELINE = Present-day emissions.

  • ALLLPG = Complete household transition from solid fuels to LPG.

  • URB15 = Partial transition of ALLLPG within 15 km of urban areas.

  • STATE50 = Emission reduction of URB15 applied evenly across each state.

  • EMIS50 = Continued solid fuel use (stacking) at 50% after transitioning to LPG.

Variables

  • PM₂.₅ = Fine particulate matter, annual-mean.

  • MORT = Annual number of premature mortalities.

  • DALYs rate = Annual rate of disability-adjusted life years per 100,000 people.

# --- modules --- 
import pandas as pd
import geopandas as gpd
from bokeh.layouts import column, row
from bokeh.models import (ColumnDataSource, CustomJS, LinearColorMapper, 
                          GeoJSONDataSource, Panel, Tabs, 
                          BasicTicker, ColorBar)
from bokeh.plotting import figure
from bokeh.themes import Theme
from bokeh.io import show, output_notebook, reset_output
from bokeh.palettes import Viridis10, BrBG10
output_notebook()

# --- data --- 
path = 'data/'
scenarios = ['BASELINE', 'ALLLPG', 'URB15', 'EMIS50', 'STATE50']
gdf = gpd.read_file(path + 'gadm36_IND_1.shp')
geosources = {}
for scenario in scenarios:
    df = pd.read_csv(path + 'Conibear_et_al_2020_supp-data_' + scenario + '.csv')[0:34]
    df.rename(columns={'Unnamed: 0': 'geo-id'}, inplace=True)
    df['scenario'] = scenario
    gdf_merged = gdf.merge(df, left_on='NAME_1', right_on='location')
    geosource = GeoJSONDataSource(geojson=gdf_merged.to_json())
    geosources.update({scenario: geosource})
    
# --- formating --- 
df_formats = {}

plot_labels = [
    "Ambient PM\u2082.\u2085 concentrations",
    "Household PM\u2082.\u2085 concentrations, females",
    "Household PM\u2082.\u2085 concentrations, males",
    "Household PM\u2082.\u2085 concentrations, children",
    "MORT from total PM\u2082.\u2085 exposure",
    "MORT from ambient PM\u2082.\u2085 exposure",
    "MORT from household PM\u2082.\u2085 exposure",
    "DALYs rate from total PM\u2082.\u2085 exposure",
    "DALYs rate from ambient PM\u2082.\u2085 exposure",
    "DALYs rate from household PM\u2082.\u2085 exposure"
]
plot_variables = [
    'apm25_popweighted',
    'hpm25_female_popweighted',
    'hpm25_male_popweighted',
    'hpm25_child_popweighted',
    'mort_tpm25_6cod_mean_total',
    'mort_apm25_6cod_mean_total',
    'mort_hpm25_6cod_mean_total',
    'dalys_tpm25_rate_6cod_mean_total',
    'dalys_apm25_rate_6cod_mean_total',
    'dalys_hpm25_rate_6cod_mean_total'
]
min_values_abs = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
max_values_abs = [100, 300, 300, 300, 150000, 150000, 150000, 3500, 3500, 3500]
min_values_diff = [-50, -300, -300, -300, -50000, -50000, -50000, -1500, -1500, -1500]
max_values_diff = [50, 300, 300, 300, 50000, 50000, 50000, 1500, 1500, 1500]
df_baseline = pd.read_csv(path + 'Conibear_et_al_2020_supp-data_BASELINE.csv')

for scenario in scenarios:
    df = pd.read_csv(path + 'Conibear_et_al_2020_supp-data_' + scenario + '.csv')
    if scenario == 'BASELINE':
        plot_title = [
            ('Ambient ' + u'PM\u2082.\u2085' + ' concentration', '(India = ' + str(df.apm25_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ")"),
            ('Household ' + u'PM\u2082.\u2085' + ' concentration, females', '(India = ' + str(df.hpm25_female_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ')'),
            ('Household ' + u'PM\u2082.\u2085' + ' concentration, males', '(India = ' + str(df.hpm25_male_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ')'),
            ('Household ' + u'PM\u2082.\u2085' + ' concentration, children', '(India = ' + str(df.hpm25_child_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ')'),
            ('MORT from total ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_tpm25_6cod_mean_total.values[-1], -3))) + ')'),
            ('MORT from ambient ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_apm25_6cod_mean_total.values[-1], -3))) + ')'),
            ('MORT from household ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_hpm25_6cod_mean_total.values[-1], -3))) + ')'),
            ('DALYs rate from total ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_tpm25_rate_6cod_mean_total.values[-1]))) + ')'),
            ('DALYs rate from ambient ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_apm25_rate_6cod_mean_total.values[-1]))) + ')'),
            ('DALYs rate from household ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_hpm25_rate_6cod_mean_total.values[-1]))) + ')')
        ]
    else:
        plot_title = [
            ('Ambient ' + u'PM\u2082.\u2085' + ' concentration', '(India = ' + str(df.apm25_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ", " + str(int(100 * df.apm25_popweighted.values[-1] / df_baseline.apm25_popweighted.values[-1])) + "%)"),
            ('Household ' + u'PM\u2082.\u2085' + ' concentration, females', '(India = ' + str(df.hpm25_female_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ", " + str(int(100 * df.hpm25_female_popweighted.values[-1] / df_baseline.hpm25_female_popweighted.values[-1])) + "%)"),
            ('Household ' + u'PM\u2082.\u2085' + ' concentration, males', '(India = ' + str(df.hpm25_male_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ", " + str(int(100 * df.hpm25_male_popweighted.values[-1] / df_baseline.hpm25_male_popweighted.values[-1])) + "%)"),
            ('Household ' + u'PM\u2082.\u2085' + ' concentration, children', '(India = ' + str(df.hpm25_child_popweighted.values[-1]) + ' ' + u'\u03bcg' + '/' + u'm\u00b3' + ", " + str(int(100 * df.hpm25_child_popweighted.values[-1] / df_baseline.hpm25_child_popweighted.values[-1])) + "%)"),
            ('MORT from total ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_tpm25_6cod_mean_total.values[-1], -3))) + ", " + str(int(100 * df.mort_tpm25_6cod_mean_total.values[-1] / df_baseline.mort_tpm25_6cod_mean_total.values[-1])) + "%)"),
            ('MORT from ambient ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_apm25_6cod_mean_total.values[-1], -3))) + ", " + str(int(100 * df.mort_apm25_6cod_mean_total.values[-1] / df_baseline.mort_apm25_6cod_mean_total.values[-1])) + "%)"),
            ('MORT from household ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.mort_hpm25_6cod_mean_total.values[-1], -3))) + ", " + str(int(100 * df.mort_hpm25_6cod_mean_total.values[-1] / df_baseline.mort_hpm25_6cod_mean_total.values[-1])) + "%)"),
            ('DALYs rate from total ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_tpm25_rate_6cod_mean_total.values[-1]))) + ", " + str(int(100 * df.dalys_tpm25_rate_6cod_mean_total.values[-1] / df_baseline.dalys_tpm25_rate_6cod_mean_total.values[-1])) + "%)"),
            ('DALYs rate from ambient ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_apm25_rate_6cod_mean_total.values[-1]))) + ", " + str(int(100 * df.dalys_apm25_rate_6cod_mean_total.values[-1] / df_baseline.dalys_apm25_rate_6cod_mean_total.values[-1])) + "%)"),
            ('DALYs rate from household ' + u'PM\u2082.\u2085' + ' exposure', '(India = ' + '{:,}'.format(int(round(df.dalys_hpm25_rate_6cod_mean_total.values[-1]))) + ", " + str(int(100 * df.dalys_hpm25_rate_6cod_mean_total.values[-1] / df_baseline.dalys_hpm25_rate_6cod_mean_total.values[-1])) + "%)")
        ]
        
    format_data_abs = []
    format_data_diff = []
    for plot_index in range(10):    
        format_data_abs.append(
            (
                plot_labels[plot_index],
                plot_variables[plot_index],
                min_values_abs[plot_index],
                max_values_abs[plot_index],
                '0,0',
                plot_title[plot_index][0],
                plot_title[plot_index][1]
            )
        )
        format_data_diff.append(
            (
                plot_labels[plot_index],
                plot_variables[plot_index],
                min_values_diff[plot_index],
                max_values_diff[plot_index],
                '0,0',
                plot_title[plot_index][0],
                plot_title[plot_index][1]
            )
        )
        
    if scenario == 'BASELINE':
        df_format = pd.DataFrame(format_data_abs, columns=['variable_map', 'variable' , 'min_range', 'max_range' , 'format', 'verbage', 'verbage_value'])
    else:
        df_format = pd.DataFrame(format_data_diff, columns=['variable_map', 'variable' , 'min_range', 'max_range' , 'format', 'verbage', 'verbage_value'])
        
        
    df_format['scenario'] = scenario
    df_formats.update({scenario: df_format})
    
# --- functions ---
def create_plot(scenario, variable):
    geosource = geosources[scenario]
    df_format = df_formats[scenario].loc[df_formats[scenario].variable == variable]
    
    if scenario == 'BASELINE':
        palette = Viridis10
        color_mapper = LinearColorMapper(
            palette=palette, 
            low=df_format['min_range'].values[0],
            high=df_format['max_range'].values[0])
    else:
        palette = BrBG10
        color_mapper = LinearColorMapper(
            palette=palette,
            low=df_format['min_range'].values[0],
            high=df_format['max_range'].values[0])
    
    plot = figure(
        plot_height=500,
        plot_width=600,
        title=f'{scenario}, {df_format.verbage.values[0]} {df_format.verbage_value.values[0]}',
        tools="pan,wheel_zoom,reset,hover,save",
        x_axis_location=None,
        y_axis_location=None,
        tooltips=[
            ("Scenario", scenario),
            ("State", "@NAME_1"),
            (df_format.verbage.values[0], f"@{variable} \u03BCg/m\u00b3"),
            ("(Lon, Lat)", "($x, $y)")])
    plot.grid.grid_line_color = None
    plot.hover.point_policy = "follow_mouse"
    plot.patches(
        'xs', 'ys', source=geosource, 
        fill_color={'field': variable, 'transform': color_mapper},
        fill_alpha=0.7, line_color="grey", line_width=0.5)
    color_bar = ColorBar(
        color_mapper=color_mapper, ticker=BasicTicker(),
        label_standoff=12, border_line_color=None, location=(0,0))
    plot.add_layout(color_bar, 'right')
    return plot

# -- create interactive plot ---
tabs = []

scenario = 'BASELINE'
variable = 'apm25_popweighted'
plot = create_plot(scenario, variable)
tab = Panel(child=plot, title=scenario)
tabs.append(tab)

scenario = 'ALLLPG'
variable = 'apm25_popweighted'
df_format = df_formats[scenario].loc[df_formats[scenario].variable == variable]
plot = create_plot(scenario, variable)
tab = Panel(child=plot, title=scenario)
tabs.append(tab)
    
show(Tabs(tabs=tabs))
Loading BokehJS ...